/*
 * Copyright 2011 PA Consulting Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.prodeagle.java;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Transaction;

/**
 * A helper class for storing information into the datastore
 * @author Edward Hartwell Goose
 *
 */
public class DatastoreManager {
	
	private static final String COUNTER_NAMES_SHARD = "CounterNamesShard";
	private static final Logger _logger = Logger.getLogger(DatastoreManager.class.getName());
	
	public static enum ResultType { ADD_SUCCESS, ADD_FULL, ADD_FAIL };
	
	/**
	 * Gets or creates an empty CounterNamesShard with the given key
	 * @param id - the id to turn into a key
	 * @return - the stored entity, or a newly stored entity
	 */
	public static Entity getOrInsertCounterNamesShard(long id) {
		try {
			Key key = KeyFactory.createKey(COUNTER_NAMES_SHARD, id);
		
			return getOrInsertCounterNamesShard(key);
		} catch (Exception e) {
			_logger.severe("Unexpected failure: " + e);
			return null;
		}
	}

	/**
	 * Gets or creates an empty CounterNamesShard with the given key
	 * @param key
	 * @return - the stored entity, or a newly stored entity
	 */
	public static Entity getOrInsertCounterNamesShard(Key key) {
		DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
		
		try {
			Entity entity = datastoreService.get(key);
			_logger.info("Got entity: " + entity);
			return entity;
		} catch (EntityNotFoundException e) {
			//if the entity doesn't exist, create it
			_logger.info("Entity doesn't exist, creating and inserting");
			Entity entity = new Entity(key);
			
			entity.setUnindexedProperty("names", new ArrayList<String>());
			entity.setProperty("timestamp", new Date());
			
			datastoreService.put(entity);
			
			return entity;
		}
	}

	/**
	 * Stores an entity containing the names of all the counters we know about
	 * @param id - the id of the shard
	 * @param names - the list of names to store
	 * @return - a ResultType enum indicating the level of success/failure
	 */
	public static ResultType addNames(long id, Set<String> names) {
		Key key = KeyFactory.createKey(COUNTER_NAMES_SHARD, id);
		
		_logger.info("Key to add names for: " + key);
		
		DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();

		Transaction txn = datastoreService.beginTransaction();
		
		try {
			Entity entity = datastoreService.get(txn, key);
			
			@SuppressWarnings("unchecked")
			List<String> localNames = (List<String>) entity.getProperty("names");
			
			if (null == localNames) {
				_logger.info("names property is null. Creating new names property list");
				localNames = new ArrayList<String>();
			}
			
			Set<String> localNamesSet = new HashSet<String>(localNames);
			for (String name : names) {
				if (!localNamesSet.contains(name)) {
					localNamesSet.add(name);
				}
			}
			
			localNames = new ArrayList<String>(localNamesSet);
			
			try {
				entity.setProperty("names", localNames);
				
				datastoreService.put(txn, entity);
				
				txn.commit();

				return ResultType.ADD_SUCCESS;
			} catch (Exception e) {
				_logger.warning("Failure to save entity with names: " + e);
				if (localNames.size() >= 5000) { //python has an error about size, but we'll just artificially limit it to 1000 here
					return ResultType.ADD_FULL;
				} else {
					return ResultType.ADD_FAIL;
				}
			}
		} catch (Exception e) {
			return ResultType.ADD_FAIL;
		} finally {
			if (txn.isActive()) {
				_logger.info("Rolling back transaction");
				txn.rollback();
			}
		}
	}
}
